package com.fengye.redisson.star.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fengye.redisson.star.domain.Praise;
import com.fengye.redisson.star.dto.PraiseDTO;
import com.fengye.redisson.star.dto.PraiseRankDTO;
import com.fengye.redisson.star.mapper.PraiseMapper;
import com.fengye.redisson.star.service.PraiseService;
import com.fengye.redisson.star.service.RedisPraiseService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 *
 */
@Service
@Slf4j
public class PraiseServiceImpl extends ServiceImpl<PraiseMapper, Praise>
        implements PraiseService {

    @Autowired
    RedissonClient client;

    @Autowired
    RedisPraiseService redisPraiseService;

    @Resource
    PraiseMapper praiseMapper;

    @Value("${redisson.lock.blog:redisson-lock-blog}")
    private String redissonBlogLock;

    /**
     * 根据用户 Id 和博客 Id 查询出点赞记录
     * @param blogId 博客
     * @param userId 用户
     * @return com.fengye.redisson.star.domain.Praise
     */
    private Praise selectByBlogIdAndUserId(Integer blogId, Integer userId) {
        QueryWrapper<Praise> wrapper = new QueryWrapper<>();
        wrapper.eq("blog_id", blogId).eq("user_id", userId);
        return this.getOne(wrapper);
    }

    private int countByBlogId(Integer blogId) {
        QueryWrapper<Praise> wrapper = new QueryWrapper<>();
        wrapper.eq("blog_id", blogId).eq("is_active", 1).eq("status", 1);
        return this.count();
    }

    private boolean cancelPraiseBlog(Integer blogId, Integer userId) {
        QueryWrapper<Praise> wrapper = new QueryWrapper<>();
        wrapper.eq("blog_id", blogId).eq("user_id", userId);
        wrapper.eq("is_active", 1);
        wrapper.eq("status", 1);

        Praise praise = new Praise();
        praise.setStatus(0);
        boolean update = this.update(praise, wrapper);
        return update;
    }

    private boolean savePraise(Integer userId, Integer blogId) {
        Praise p = new Praise();
        p.setBlogId(blogId);
        p.setUserId(userId);

        p.setPraiseTime(new Date());

        p.setStatus(1);
        p.setCreateTime(new Date());

        boolean save = this.save(p);

        return save;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPraise(PraiseDTO dto) throws Exception {
        Praise p = null;
        try {
            p = this.selectByBlogIdAndUserId(dto.getBlogId(), dto.getUserId());
        } catch (Exception e) {
            log.error("查询错误,结果不止一条");
            throw e;
        }
        if (p == null) { // 没有点赞记录,新增点赞，并记录到数据库
            boolean save = this.savePraise(dto.getUserId(), dto.getBlogId());
            if (save) {
                log.info("无锁 - 点赞成功，dto={}", dto);

                redisPraiseService.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 1);

                //触发更新缓存中的排行榜
                this.cachePraiseTotal();
            }
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPraiseWithLock(PraiseDTO dto) throws Exception {
        Praise p = null;
        try {
            p = this.selectByBlogIdAndUserId(dto.getBlogId(), dto.getUserId());
        } catch (Exception e) {
            log.error("查询错误,结果不止一条");
            throw e;
        }


        final String lockName = redissonBlogLock + dto.getBlogId() + "-" + dto.getUserId();
        RLock lock = client.getLock(lockName);


        try {
            lock.lock(10L, TimeUnit.SECONDS);
            boolean save = this.savePraise(dto.getUserId(), dto.getBlogId());
            if (save) {
                log.info("Redisson 锁 - 点赞成功，dto={}", dto);

                redisPraiseService.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 1);

                //触发更新缓存中的排行榜
                this.cachePraiseTotal();
            }
        } catch (Exception e) {
            throw e;
        } finally {
            if (lock != null) {
                lock.unlock();
            }

        }
    }

    @Override
    public Long getBlogPraiseTotal(Integer blogId) throws Exception {
        return redisPraiseService.getCacheTotalBlog(blogId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelPraise(PraiseDTO dto) throws Exception {
        //判断当前参数的合法性
        if (dto.getBlogId() != null && dto.getUserId() != null) {
            //当前用户取消点赞博客-更新相应的记录信息
            int result = praiseMapper.cancelPraiseBlog(dto.getBlogId(), dto.getUserId());
            //判断是否更新成功
            if (result > 0) {
                //result>0表示更新成功，则同时更新缓存中相应博客的用户点赞记录
                log.info("---取消点赞博客-{}-更新点赞记录成功---", dto.getBlogId());
                redisPraiseService.cachePraiseBlog(dto.getBlogId(), dto.getUserId(), 0);


                //触发更新缓存中的排行榜
                this.cachePraiseTotal();
            }
        }
    }

    @Override
    public Collection<PraiseRankDTO> getRankWithRedisson() throws Exception {
        return redisPraiseService.getBlogPraiseRank();
    }


    /**
     * 将当前博客id对应的点赞总数构造为实体，并添加进SortedSet中 - 构造排行榜
     * 记录当前博客id-点赞总数-实体排行榜
     */
    private void cachePraiseTotal() {
        try {
            redisPraiseService.rankBlogPraise();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}




